﻿/*
Copyright © 2011 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NUnit.Framework.Api;
using NUnit.Framework.Internal;
using NUnitLite.Runner;

namespace NUnit.Phone.Gui
{
    /// <summary>
    /// Manages the loading and execution of the unit tests.
    /// </summary>
    /// <remarks>
    /// This class is the entry point for the initial configuration of the
    /// test assemblies to be tested.
    /// </remarks>
    public class TestManager
    {
        /// <summary>
        /// The unique instance of the class.
        /// </summary>
        private static TestManager instance;

        /// <summary>
        /// The list of the test assemblies.
        /// </summary>
        private readonly ICollection<Assembly> assemblies;

        /// <summary>
        /// A value indicating whether the manager is considered as
        /// initialized.
        /// </summary>
        private bool initialized;

        /// <summary>
        /// The root test.
        /// </summary>
        private ITest rootTest;

        /// <summary>
        /// The associated test runner.
        /// </summary>
        private TestRunner runner;

        /// <summary>
        /// Prevents a default instance of the TestManager class from
        /// being created.
        /// </summary>
        private TestManager()
        {
            this.assemblies = new LinkedList<Assembly>();
        }

        /// <summary>
        /// Gets the unique instance of the class.
        /// </summary>
        public static TestManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new TestManager();
                }

                return instance;
            }
        }

        /// <summary>
        /// Gets the root test associated with the manager.
        /// </summary>
        public ITest RootTest
        {
            get
            {
                this.InitializeTests();
                return this.rootTest;
            }
        }

        /// <summary>
        /// Gets the test runner associated with the manager.
        /// </summary>
        public TestRunner Runner
        {
            get
            {
                this.InitializeTests();
                return this.runner;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the tests
        /// should be automatically started.
        /// </summary>
        public bool AutoStart { get; set; }

        /// <summary>
        /// Adds a new test assembly to the manager.
        /// </summary>
        /// <param name="assembly">The test assembly to run.</param>
        public void AddAssembly(Assembly assembly)
        {
            lock (this)
            {
                if (this.initialized)
                {
                    throw new NotSupportedException();
                }
                if (assembly == null)
                {
                    throw new ArgumentNullException("assembly");
                }

                this.assemblies.Add(assembly);
            }
        }

        /// <summary>
        /// Retrieves a specific test.
        /// </summary>
        /// <param name="identifier">The identifier of the test.</param>
        /// <returns>
        /// The test associated with <paramref name="identifier"/> or <c>null</c>.
        /// </returns>
        public ITest RetrieveTest(int identifier)
        {
            this.InitializeTests();
            if (this.RootTest != null)
            {
                return RetrieveTest(this.RootTest, identifier);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Try to retrieves a specific test recursively.
        /// </summary>
        /// <param name="reference">The current reference test.</param>
        /// <param name="identifier">The identifier of the searched test.</param>
        /// <returns>
        /// The test associated with <paramref name="identifier"/> or <c>null</c>.
        /// </returns>
        private static ITest RetrieveTest(ITest reference, int identifier)
        {
            if (reference.Id == identifier)
            {
                return reference;
            }

            if (reference.HasChildren)
            {
                foreach (ITest child in reference.Tests)
                {
                    ITest result = RetrieveTest(child, identifier);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Loads all the test assemblies to initialize the
        /// root test and the associated test runner.
        /// </summary>
        private void InitializeTests()
        {
            if (this.initialized)
            {
                return;
            }

            lock (this)
            {
                this.initialized = true;

                if (this.assemblies.Count == 0)
                {
                    throw new NotSupportedException();
                }

                Test test;
                if (this.assemblies.Count == 1)
                {
                    test = TestLoader.Load(assemblies.First());
                }
                else
                {
                    TestSuite suite = new TestSuite("global");
                    foreach (Assembly assembly in this.assemblies)
                    {
                        suite.Add(TestLoader.Load(assembly));
                    }
                    test = suite;
                }

                this.rootTest = test;
                this.runner = new TestRunner(test);
            }
        }
    }
}
